package uc

import (
	"database/sql"
	"go.uber.org/zap"
	"strconv"
	"strings"
	"sync"
	"wzgames/internal/client"
	"wzgames/internal/constant"
	"wzgames/internal/gdao/walletdao"
	"wzgames/internal/gloader"
	"wzgames/internal/model/dwp"
	"wzgames/internal/model/mcomm"
	"wzgames/internal/model/user"
	"wzgames/internal/response"
	"wzgames/pkg/components/mysql"
	"wzgames/pkg/logger"
	"wzgames/pkg/utils/calcutil"
	"wzgames/pkg/utils/dateutil"
	"wzgames/pkg/utils/stringutil"
)

// 钱包转账 余额信息 转账查询

var (
	walletMgr *WalletManager
	oncew     sync.Once
)

type WalletManager struct {
	walletDs *mysql.DS
}

func WalletMgr() *WalletManager {
	if walletMgr == nil {
		oncew.Do(func() {
			walletMgr = &WalletManager{}
			walletMgr.walletDs = gloader.CommMgr().GetDBProxy(constant.MysqlOfWallet)
		})
	}

	return walletMgr
}

// Balance 获取余额信息
func (w *WalletManager) Balance(req *dwp.BalanceAuthReply) (int16, *dwp.BalanceData) {
	wDao := walletdao.WalletDao{Driver: w.walletDs.Ds()}
	wallet, err := wDao.GetWalletByUid(req.Uid, req.CurrID)
	if err != nil {
		if err != sql.ErrNoRows {
			logger.Error("[UC] balance Error on GetWalletByUid", zap.Int64("AgtId", req.AgtID), zap.String("User", req.Uname), zap.String("Currency", req.Curr), zap.Error(err))
			return response.ErrInternal, nil
		}
		return 0, &dwp.BalanceData{Uid: req.AgtUid, Username: req.Uname, Currency: req.Curr, Balance: "0"}
	}

	return 0, &dwp.BalanceData{Uid: req.AgtUid, Username: req.Uname, Currency: req.Curr, Balance: wallet.Balance}
}

// checkBTxId 根据B端的转账单号来查询转账信息
func (w *WalletManager) checkBTxId(agentOrderNo string, agtId int64, actype mcomm.WalletAction) (int16, string, *user.WalletTxHash) {
	wDao := walletdao.WalletDao{Driver: w.walletDs.Ds()}
	var sb strings.Builder
	sb.WriteString(strconv.FormatInt(agtId, 10))
	sb.WriteString(constant.UnderScore)
	sb.WriteString(stringutil.ToString(actype))
	sb.WriteString(constant.UnderScore)
	sb.WriteString(agentOrderNo)
	bNo := sb.String()
	c, err := wDao.GetTidByBTxNo(bNo)
	if err != nil {
		if err != sql.ErrNoRows {
			logger.Error("[UC] check B txId Error on checkBTxId", zap.Int64("AgtId", agtId), zap.String("BTxId", agentOrderNo), zap.Int32("AcType", int32(actype)), zap.Error(err))
			return response.ErrInternal, "", nil
		}
		return 0, bNo, nil
	}

	return response.ErrDuplicate, bNo, c
}

func (w *WalletManager) getDuplicateTransferInfo(txId uint64, agtUid string) (int16, *dwp.DepWitData) {
	wDao := walletdao.WalletDao{Driver: w.walletDs.Ds()}
	c, err := wDao.GetTransferByTxId(txId)
	if err != nil {
		if err == sql.ErrNoRows {
			return response.ErrNotFound, nil
		}
		logger.Error("[UC] get transfer Error on GetTransferByTxId", zap.Uint64("TxId", txId), zap.String("AgtUid", agtUid), zap.Error(err))
		return response.ErrInternal, nil
	}

	return response.ErrDuplicate, &dwp.DepWitData{
		Uid:        agtUid,
		Username:   c.Uname,
		BTxId:      c.FromId,
		GTxId:      strconv.FormatUint(txId, 10),
		NewBalance: c.Balance,
		OldBalance: c.OBalance,
		Currency:   c.UCurr,
	}
}

// LoadWallet 查询或创建对应币种钱包
func (w *WalletManager) LoadWallet(agtId int64, uid uint64, currId int32) (int16, *user.WalletEntity) {
	wDao := walletdao.WalletDao{Driver: w.walletDs.Ds()}
	wallet, err := wDao.GetWalletByUid(uid, currId)
	toCreated := false
	if err != nil {
		if err != sql.ErrNoRows {
			logger.Error("[UC] load wallet Error on LoadWallet", zap.Int64("AgtId", agtId), zap.Uint64("Uid", uid), zap.Int32("CurrID", currId), zap.Error(err))
			return response.ErrInternal, nil
		}
		toCreated = true
	}
	if toCreated {
		sysCurr := gloader.CommMgr().GetCurrencyByID(currId)
		if sysCurr == nil {
			logger.Warn("[UC] load wallet fail on invalid currency", zap.Int64("AgtId", agtId), zap.Uint64("Uid", uid), zap.Int32("CurrID", currId))
			return response.ErrCurrency, nil
		}
		t := dateutil.UTC()
		wallet = &user.WalletEntity{
			Uid:     uid,
			AgtId:   agtId,
			CurrID:  currId,
			Curr:    sysCurr.Curr,
			Balance: "0",
			Wv:      1,
			Created: t,
			Updated: t,
		}
		err = wDao.CreateWallet(wallet)
		if err != nil {
			logger.Error("[UC] load wallet Error on CreateWallet", zap.Int64("AgtId", agtId), zap.Uint64("Uid", uid), zap.Int32("CurrID", currId), zap.Error(err))
			return response.ErrInternal, nil
		}
	}

	return 0, wallet
}

// Deposit 转入
func (w *WalletManager) Deposit(req *dwp.DepWitAuthReply) (int16, *dwp.DepWitData) {
	sysCurr := gloader.CommMgr().GetCurrencyByID(req.CurrID)
	if sysCurr == nil {
		logger.Warn("[UC] wallet deposit split amount fail on invalid currency", zap.Int64("AgtId", req.AgtID), zap.String("User", req.Uname), zap.String("Currency", req.Curr), zap.String("BTxId", req.AgtOrderId))
		return response.ErrCurrency, nil
	}
	// 判断是否重复单号
	c0, bNo, txE := w.checkBTxId(req.AgtOrderId, req.AgtID, mcomm.WalletAction_Deposit)
	if c0 != 0 {
		if c0 == response.ErrDuplicate {
			return w.getDuplicateTransferInfo(txE.TxId, req.AgtUid)
		}
		return c0, nil
	}
	c1, wallet := w.LoadWallet(req.AgtID, req.Uid, req.CurrID)
	if c1 != 0 {
		return c1, nil
	}
	txId := client.GetSonyFlakeID() // G端转账单号
	if txId == 0 {
		logger.Error("[UC] deposit fail on gen txId on GetSonyFlakeID", zap.Int64("AgtId", req.AgtID), zap.String("User", req.Uname), zap.String("Currency", req.Curr), zap.String("BTxId", req.AgtOrderId))
		return response.ErrInternal, nil
	}

	tx, err := w.walletDs.Ds().Beginx()
	if err != nil {
		logger.Error("[UC] create deposit fail on start tx", zap.Int64("AgtId", req.AgtID), zap.String("User", req.Uname), zap.String("Currency", req.Curr), zap.String("BTxId", req.AgtOrderId), zap.Error(err))
		return response.ErrInternal, nil
	}
	defer tx.Rollback()

	dao := walletdao.WalletDao{Driver: tx}
	t := dateutil.UTC()
	newBalance, neg := calcutil.AmountAdd(wallet.Balance, req.Amount)
	if neg {
		return response.ErrInsufficientBalance, nil
	}
	transfer := &user.WalletTransfer{
		TxId:     txId,
		Uid:      req.Uid,
		AgtId:    req.AgtID,
		AgtUid:   req.AgtUid,
		Uname:    req.Uname,
		CurrID:   req.CurrID,
		UCurr:    req.Curr,
		AcType:   mcomm.WalletAction_Deposit,
		Status:   mcomm.AcStatus_Effect,
		Amount:   req.Amount,
		Balance:  newBalance,
		OBalance: wallet.Balance,
		FromId:   req.AgtOrderId,
		Created:  t,
		Updated:  t,
	}
	err = dao.CreateWalletTransfer(transfer)
	if err != nil {
		logger.Error("[UC] create deposit wallet transfer fail on CreateWalletTransfer", zap.Int64("AgtId", req.AgtID), zap.String("User", req.Uname), zap.String("Currency", req.Curr), zap.String("BTxId", req.AgtOrderId), zap.Error(err))
		return response.ErrInternal, nil
	}

	txHash := &user.WalletTxHash{
		FromId: bNo,
		TxId:   txId,
	}
	err = dao.CreateBTxHash(txHash)
	if err != nil {
		logger.Error("[UC] create deposit tx hash fail on CreateBTxHash", zap.Int64("AgtId", req.AgtID), zap.String("User", req.Uname), zap.String("Currency", req.Curr), zap.String("BTxId", req.AgtOrderId), zap.Error(err))
		return response.ErrInternal, nil
	}

	wallet.Updated = t
	// 乐观
	c, err1 := dao.IncWalletBalanceWithWv(wallet, req.Amount)
	if err1 != nil || c == 0 {
		logger.Error("[UC] update deposit wallet balance fail on UpdateWalletBalance", zap.Int64("AgtId", req.AgtID), zap.String("User", req.Uname), zap.String("Currency", req.Curr), zap.String("BTxId", req.AgtOrderId), zap.Error(err1))
		return response.ErrInternal, nil
	}

	err = tx.Commit()
	if err == nil {
		return 0, &dwp.DepWitData{
			Uid:        req.AgtUid,
			Username:   req.Uname,
			BTxId:      req.AgtOrderId,
			GTxId:      strconv.FormatUint(txId, 10),
			NewBalance: newBalance,
			OldBalance: wallet.Balance,
			Currency:   req.Curr,
		}
	}

	logger.Error("[UC] create deposit fail on tx commit", zap.Int64("AgtId", req.AgtID), zap.String("User", req.Uname), zap.String("Currency", req.Curr), zap.String("BTxId", req.AgtOrderId), zap.Error(err))

	return response.ErrInternal, nil
}

// Withdraw 转出
func (w *WalletManager) Withdraw(req *dwp.DepWitAuthReply) (int16, *dwp.DepWitData) {
	sysCurr := gloader.CommMgr().GetCurrencyByID(req.CurrID)
	if sysCurr == nil {
		logger.Warn("[UC] wallet withdraw split amount fail on invalid currency", zap.Int64("AgtId", req.AgtID), zap.String("User", req.Uname), zap.String("Currency", req.Curr), zap.String("BTxId", req.AgtOrderId))
		return response.ErrCurrency, nil
	}
	// 查重
	c0, bNo, txE := w.checkBTxId(req.AgtOrderId, req.AgtID, mcomm.WalletAction_Withdraw)
	if c0 != 0 {
		if c0 == response.ErrDuplicate {
			return w.getDuplicateTransferInfo(txE.TxId, req.AgtUid)
		}
		return c0, nil
	}
	c1, wallet := w.LoadWallet(req.AgtID, req.Uid, req.CurrID)
	if c1 != 0 {
		return c1, nil
	}
	newBalance, neg := calcutil.AmountSub(wallet.Balance, req.Amount)
	if neg {
		return response.ErrInsufficientBalance, nil
	}
	txId := client.GetSonyFlakeID()
	if txId == 0 {
		logger.Error("[UC] withdraw fail on gen txId on GetSonyFlakeID", zap.Int64("AgtId", req.AgtID), zap.String("User", req.Uname), zap.String("Currency", req.Curr), zap.String("BTxId", req.AgtOrderId))
		return response.ErrInternal, nil
	}

	tx, err := w.walletDs.Ds().Beginx()
	if err != nil {
		logger.Error("[UC] create withdraw fail on start tx", zap.Int64("AgtId", req.AgtID), zap.String("User", req.Uname), zap.String("Currency", req.Curr), zap.String("BTxId", req.AgtOrderId), zap.Error(err))
		return response.ErrInternal, nil
	}
	defer tx.Rollback()

	dao := walletdao.WalletDao{Driver: tx}
	t := dateutil.UTC()
	transfer := &user.WalletTransfer{
		TxId:     txId,
		Uid:      req.Uid,
		AgtId:    req.AgtID,
		AgtUid:   req.AgtUid,
		Uname:    req.Uname,
		CurrID:   req.CurrID,
		UCurr:    req.Curr,
		AcType:   mcomm.WalletAction_Withdraw,
		Status:   mcomm.AcStatus_Effect,
		Amount:   req.Amount,
		Balance:  newBalance,
		OBalance: wallet.Balance,
		FromId:   req.AgtOrderId,
		Created:  t,
		Updated:  t,
	}
	err = dao.CreateWalletTransfer(transfer)
	if err != nil {
		logger.Error("[UC] create withdraw wallet transfer fail on CreateWalletTransfer", zap.Int64("AgtId", req.AgtID), zap.String("User", req.Uname), zap.String("Currency", req.Curr), zap.String("BTxId", req.AgtOrderId), zap.Error(err))
		return response.ErrInternal, nil
	}

	txHash := &user.WalletTxHash{
		FromId: bNo,
		TxId:   txId,
	}
	err = dao.CreateBTxHash(txHash)
	if err != nil {
		logger.Error("[UC] create withdraw tx hash fail on CreateBTxHash", zap.Int64("AgtId", req.AgtID), zap.String("User", req.Uname), zap.String("Currency", req.Curr), zap.String("BTxId", req.AgtOrderId), zap.Error(err))
		return response.ErrInternal, nil
	}

	wallet.Updated = t
	// 乐观锁
	c, err1 := dao.DecWalletBalance(wallet, req.Amount)
	if err1 != nil || c == 0 {
		logger.Error("[UC] update withdraw wallet balance fail on UpdateWalletBalance", zap.Int64("AgtId", req.AgtID), zap.String("User", req.Uname), zap.String("Currency", req.Curr), zap.String("BTxId", req.AgtOrderId), zap.Error(err1))
		return response.ErrInternal, nil
	}

	err = tx.Commit()
	if err == nil {
		return 0, &dwp.DepWitData{
			Uid:        req.AgtUid,
			Username:   req.Uname,
			BTxId:      req.AgtOrderId,
			GTxId:      strconv.FormatUint(txId, 10),
			NewBalance: newBalance,
			OldBalance: wallet.Balance,
			Currency:   req.Curr,
		}
	}

	logger.Error("[UC] create withdraw fail on tx commit", zap.Int64("AgtId", req.AgtID), zap.String("User", req.Uname), zap.String("Currency", req.Curr), zap.String("BTxId", req.AgtOrderId), zap.Error(err))

	return response.ErrInternal, nil
}

// CheckTransfer 查询转账状态信息
func (w *WalletManager) CheckTransfer(req *dwp.DwCheck) (int16, *dwp.DepWitData) {
	wDao := walletdao.WalletDao{Driver: w.walletDs.Ds()}
	var actype mcomm.WalletAction
	if req.DorW == constant.KeyDeposit {
		actype = mcomm.WalletAction_Deposit
	} else {
		actype = mcomm.WalletAction_Withdraw
	}
	var sb strings.Builder
	sb.WriteString(strconv.FormatInt(req.AgtID, 10))
	sb.WriteString(constant.UnderScore)
	sb.WriteString(stringutil.ToString(actype))
	sb.WriteString(constant.UnderScore)
	sb.WriteString(req.AgtOrderId)
	bNo := sb.String()
	c, err := wDao.GetTidByBTxNo(bNo)
	if err != nil {
		if err != sql.ErrNoRows {
			logger.Error("[UC] get B txId Error on checkBTxId", zap.Int64("AgtId", req.AgtID), zap.String("BTxId", req.AgtOrderId), zap.Int32("AcType", int32(actype)), zap.Error(err))
			return response.ErrInternal, nil
		}
		return response.ErrNotFound, nil
	}

	transfer, e1 := wDao.GetTransferByTxId(c.TxId)
	if e1 != nil || transfer == nil || transfer.TxId == 0 {
		logger.Error("[UC] get transfer fail on GetTransferByTxId", zap.Int64("AgtId", req.AgtID), zap.String("BTxId", req.AgtOrderId), zap.Int32("AcType", int32(actype)), zap.Uint64("TxId", c.TxId), zap.Error(err))
		return response.ErrInternal, nil
	}

	return 0, &dwp.DepWitData{
		Uid:        transfer.AgtUid,
		Username:   transfer.Uname,
		BTxId:      transfer.FromId,
		GTxId:      strconv.FormatUint(c.TxId, 10),
		NewBalance: transfer.Balance,
		OldBalance: transfer.OBalance,
		Currency:   transfer.UCurr,
	}
}
